The present invention relates generally to system and methods for increasing reliability of software programs. More particularly, the present invention relates to a development system and methods for runtime detection of resource and memory errors occurring in software programs operative on digital computers.
Before a digital computer may accomplish a desired task, it must receive an appropriate set of instructions. Executed by the computer's microprocessor, these instructions, collectively referred to as a "computer program," direct the operation of the computer. Expectedly, the computer must understand the instructions which it receives before it may undertake the specified activity.
Owing to their digital nature, computers essentially only understand "machine code," i.e., the low-level, minute instructions for performing specific tasks--the sequence of ones and zeros that are interpreted as specific instructions by the computer's microprocessor. Since machine language or machine code is the only language computers actually understand, all other programming languages represent ways of structuring human language so that humans can get computers to perform specific tasks.
While it is possible for humans to compose meaningful programs in machine code, practically all software development today employs one or more of the available programming languages. The most widely used programming languages are the "high-level" languages, such as C or Pascal. These languages allow data structure and algorithms to be expressed in a style of writing which is easily read and understood by fellow programmers.
A program called a "compiler" translates these instructions into the requisite machine language. In the context of this translation, the program written in the high-level language is called the "source code" or source program. The ultimate output of the compiler is an "object module," which includes instructions for execution by a target processor. Although an object module includes code for instructing the operation of a computer, the object module itself is not in a form which may be directly executed by a computer. Instead, it must undergo a "linking" operation before the final executable program is created.
Linking may be thought of as the general process of combining or linking together one or more compiled object modules to create an executable program. This task usually falls to a program called a "linker." In typical operation, a linker receives, either from the user or from an integrated compiler, a list of object modules desired to be included in the link operation. The linker scans the object modules from the object and library files specified. After resolving interconnecting references as needed, the linker constructs an executable image by organizing the object code from the modules of the program in a format understood by the operating system program loader. The end result of linking is executable code (typically an .exe file) which, after testing and quality assurance, is passed to the user with appropriate installation and usage instructions.
Since computer programs are created from source code which a user supplies, the source code is an inherent source of errors. During the course of software development, therefore, substantial development resources are allocated today to the process of finding "bugs" in the software. As used herein, "bugs" refer to errors occurring in the program being developed. Bugs, for example, can be anything from taking an unexpected path in the logical flow to inadvertently writing to a wrong memory location. Expectedly, there is keen interest in finding ways to improve the "debugging" of software.
A program called a "debugger" is often employed for finding and eliminating errors in software programs. A debugger should provide certain basic services to assist programmers in finding and correcting errors in software programs. These services include breakpointing, stepping and stopping a debuggee. Other services may include inspecting and changing data in memory, symbolic debugging, source level debugging, and setting breakpoints with expression evaluation. The general topic of debuggers is well covered by the technical, trade, and patent literature. For a detailed introduction to the topic, the reader may consult Swan, T., Mastering Turbo Debugger, Hayden Books, 1990. Additional treatment of the topic may be found in Intel Corporation's 80386 Programmer's Reference Manual, Intel Corp., Santa Clara, Calif., 1986. The disclosures of the foregoing are hereby incorporated by reference.
Although debuggers are good for examining particular locations of program code for errors, whole classes of programming exist for which the "location" of the error is not easily discerned. Examples of these types of problems include memory overruns, incorrectly type-casted C.sup.++ objects, memory allocation/de-allocation errors, heap correction, and use of uninitialized data. In addition to general memory problems, similar problems occur with other resources, such as file handles, window handles, windows GDI (graphic device independent) objects, and other objects which must be freed after use. Stated generally, these comprise errors or bugs which lead to memory corruption and "resource leaks."
Prior art attempts at addressing these types of errors have focused largely on diagnosing "the problem" after the damage has occurred. In Microsoft Windows, for instance, built-in support exists for detecting an attempt to access an in valid region of memory--de-referencing a "bad pointer." The mechanism is a post-problem tool, with results that are far from perfect. De-referencing a bad pointer in a program may or may not trigger the built-in protection mechanism, depending on whether the memory being accessed belongs to the application (despite the fact that the attempted access is not what is desired). All told, there exists a variety of problems which result from user errors in the source code, which are not detected at compile time and which are difficult to diagnose with available debuggers.
A rudimentary approach to preventing memory errors is to write a wrapper function around the memory allocation function, malloc. This fairly well known technique is described, for instance, in Writing Solid Code, by Steve McGuire (Microsoft Press). Typically, the malloc routine is rewritten to include an ASSERT (macro) call, for validating the parameter passed to the call. The approach does not allow one to track the error down to the level where the corruption really occurred--what the programmer really wants. Thus, the foregoing approach does not give the information which is really desired (i.e., exactly where the corruption occurred). It also requires the programmer to maintain additional code (i.e., the programmer's own wrapper code) outside of the standard library and that he or she really understand the wrapper's interaction with the library.